/*
 * FuzzyLiteTest.cpp
 *
 *  Created on: Jan 27, 2012
 *      Author: godinho
 */

#include <iostream>
#include <string.h>
#include <stdio.h>

#include <fuzzylite/FuzzyEngine.h>
#include <fuzzylite/ShoulderTerm.h>
#include <fuzzylite/TriangularTerm.h>
#include <fuzzylite/MamdaniRule.h>

using namespace fl;
using namespace std;

#define NUM_ANTECEDENTS 5
#define MATRIX_CELL_MAX_LEN 10

char antecedents[NUM_ANTECEDENTS][MATRIX_CELL_MAX_LEN] =
                              {"TINY", "SMALL", "NORMAL", "BIG", "HUGE"};

char consequents[NUM_ANTECEDENTS][NUM_ANTECEDENTS][MATRIX_CELL_MAX_LEN] =
                             {{"MEAN_INC",  "SMALL_INC", "SMALL_INC",  "SMALL_DEC", "MEAN_DEC"},
                              {"MEAN_INC",  "SMALL_INC", "NONE",       "SMALL_DEC", "MEAN_DEC"},
                              {"SMALL_INC", "SMALL_INC", "NONE",       "SMALL_DEC", "MEAN_DEC"},
                              {"SMALL_INC", "NONE",      "NONE",       "SMALL_DEC", "MEAN_DEC"},
                              {"NONE",      "NONE",      "SMALL_DEC",  "MEAN_DEC",  "MEAN_DEC"}};

typedef struct {
    FuzzyEngine* engine;
    InputLVar* sideQueue;
    InputLVar* nextQueue;
    OutputLVar* extension;
} FuzzyContext;

void insertQueueLiguisticTerms(LinguisticVariable* queueLinguisticVar) {
    queueLinguisticVar->addTerm(new ShoulderTerm("TINY", 3, 6, true));
    queueLinguisticVar->addTerm(new TriangularTerm("SMALL", 3, 9));
    queueLinguisticVar->addTerm(new TriangularTerm("NORMAL", 6, 12));
    queueLinguisticVar->addTerm(new TriangularTerm("BIG", 9, 15));
    queueLinguisticVar->addTerm(new ShoulderTerm("HUGE", 12, 15, false));
}

void insertExtensionLinguisticTerms(LinguisticVariable* extensionLinguisticVar) {
    extensionLinguisticVar->addTerm(new ShoulderTerm("BIG_DEC", -12, -8, true));
    extensionLinguisticVar->addTerm(new TriangularTerm("MEAN_DEC", -12, -4));
    extensionLinguisticVar->addTerm(new TriangularTerm("SMALL_DEC", -8, 0));
    extensionLinguisticVar->addTerm(new TriangularTerm("NONE", -4, 4));
    extensionLinguisticVar->addTerm(new TriangularTerm("SMALL_INC", 0, 8));
    extensionLinguisticVar->addTerm(new TriangularTerm("MEAN_INC", 4, 12));
    extensionLinguisticVar->addTerm(new ShoulderTerm("BIG_INC", 8, 12, false));
}

void insertRules(RuleBlock* block, FuzzyEngine* engine) {
    char rule[200];
    for(int x = 0; x < NUM_ANTECEDENTS; x++) {
        for(int y = 0; y < NUM_ANTECEDENTS; y++) {
            sprintf(rule, "if SideQueue is %s and NextQueue is %s then Extension is %s",
                    antecedents[x], antecedents[y], consequents[x][y]);
            block->addRule(new MamdaniRule(rule, *engine));
        }
    }
}

void initFuzzyContext(FuzzyContext* fctx) {

    FuzzyEngine* engine = new FuzzyEngine();
    fctx->engine = engine;

    InputLVar* sideQueue = new InputLVar("SideQueue");
    insertQueueLiguisticTerms(sideQueue);
    engine->addInputLVar(sideQueue);
    fctx->sideQueue = sideQueue;

    InputLVar* nextQueue = new InputLVar("NextQueue");
    insertQueueLiguisticTerms(nextQueue);
    engine->addInputLVar(nextQueue);
    fctx->nextQueue = nextQueue;

    OutputLVar* extension = new OutputLVar("Extension");
    insertExtensionLinguisticTerms(extension);
    engine->addOutputLVar(extension);
    fctx->extension = extension;

    RuleBlock* block = new RuleBlock();
    insertRules(block, engine);
    engine->addRuleBlock(block);

}

void printLine(ostream& stream, int cellSize, int numCell) {
    stream << "\n+";
    for(int j = 0; j < numCell; j++) {
        for(int i = 0; i < cellSize; i++) {
            stream << "-";
        }
        stream << "+";
    }
    stream << "\n";
}

void printCell(ostream& stream, char* value, int width) {
    char cell[200];
    sprintf(cell, "%-*s", MATRIX_CELL_MAX_LEN-1, value);
    stream << cell << " | ";
}

void printRules(ostream& stream) {

    int cellLen = MATRIX_CELL_MAX_LEN-1;

    printLine(stream, MATRIX_CELL_MAX_LEN + 1, NUM_ANTECEDENTS+1);
    stream << "| ";
    printCell(stream, "SQ/NQ", cellLen);
    for(int i = 0; i < NUM_ANTECEDENTS; i++) {
        printCell(stream, antecedents[i], cellLen);
    }
    printLine(stream, MATRIX_CELL_MAX_LEN + 1, NUM_ANTECEDENTS+1);

    for(int i = 0; i < NUM_ANTECEDENTS; i++) {
        stream << "| ";
        printCell(stream, antecedents[i], cellLen);

        for(int j = 0; j < NUM_ANTECEDENTS; j++) {
            printCell(stream, consequents[i][j], cellLen);
        }

        printLine(stream, MATRIX_CELL_MAX_LEN + 1, NUM_ANTECEDENTS+1);
    }

    stream << "\n";

    stream << "SQ - Side Queue \n";
    stream << "NQ - Next Queue";


}

int main(int argc, char** argv) {

    FuzzyContext fuzzyContext;

    initFuzzyContext(&fuzzyContext);

    printRules(cout);
    cout << "\n\n";

    int sideQueue = 0;
    int nextQueue = 0;

    cout << "Next Queue: ";
    cin >> nextQueue;

    cout << "Side Queue: ";
    cin >> sideQueue;

    fuzzyContext.nextQueue->setInput(nextQueue);
    fuzzyContext.sideQueue->setInput(sideQueue);
    fuzzyContext.engine->process();
    flScalar extensionNum = fuzzyContext.extension->output().defuzzify();

    cout << "Next Queue is: " << fuzzyContext.nextQueue->fuzzify(nextQueue);
    cout << "Side Queue is: " << fuzzyContext.sideQueue->fuzzify(sideQueue);
    cout << "Extension value: " << (int) extensionNum << "\n";
    cout << "Extension is: " << fuzzyContext.extension->fuzzify(extensionNum) << "\n";

    return 0;
}

